package api

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/smartwalle/alipay/v3"
	"gopkg.in/gomail.v2"
	"gorm.io/gorm"
	"html/template"
	"konglu-pay/common/request"
	"konglu-pay/common/response"
	"konglu-pay/constants"
	"konglu-pay/global"
	"konglu-pay/model"
	"konglu-pay/util"
	"strconv"
	"strings"
	"time"
)

type AlipayApi struct {
}

/*
		当面付Face2facePay
	  - https://juejin.cn/post/6934007551755304974
	    主要流程: Base64 ==> 解码 ==> RSA解密 ==> 反序列化 ==> 验签 ==> 进行支付宝调用
*/
func (s *AlipayApi) Face2facePay(c *gin.Context) {

	encodeReq := new(request.EncodeAlipayTradePreCreateRequest)
	if err := c.ShouldBindJSON(encodeReq); err != nil {
		response.FailWithValidateErrorWithReq(c, encodeReq, err)
		return
	}

	// base64解码
	decodeBytes, err := base64.StdEncoding.DecodeString(encodeReq.Base64)
	if err != nil {
		global.Log.Errorf("decode request error: %v", err)
		response.FailWithMsg(c, "非法无效参数，请勿刷接口")
		return
	}

	// rsa解密
	decryptBytes := util.RsaDecrypt(decodeBytes, "rsa/private_key.pem")
	if decryptBytes == nil {
		response.FailWithMsg(c, "非法无效参数，请勿刷接口")
		return
	}

	req := new(request.AlipayTradePreCreateRequest)
	err = json.Unmarshal(decryptBytes, req)
	if err != nil {
		global.Log.Errorf("unmarshal decrypt request error: %v", err)
		response.FailWithMsg(c, "系统内部异常，请联系管理员")
		return
	}

	alipayConfig := global.Setting.AlipayConfig
	// 进行验签
	signature := fmt.Sprintf("%s%s", alipayConfig.Signature, util.MD5(req.OutTradeNo))
	if signature != req.Signature {
		response.FailWithMsg(c, "非法请求，请勿刷接口")
		return
	}

	tradeWapPay := alipay.TradePreCreate{
		Trade: alipay.Trade{
			NotifyURL:      alipayConfig.NotifyUrl,
			Subject:        req.Subject,
			OutTradeNo:     req.OutTradeNo,
			TotalAmount:    req.TotalAmount,
			TimeoutExpress: req.TimeoutExpress,
		},
	}

	tradePreCreateRsp, err := global.AlipayClient.TradePreCreate(tradeWapPay)
	if err != nil {
		global.Log.Errorf("face2face tradewappay error: %v", err)
		response.FailWithMsg(c, "获取支付宝二维码异常，请联系管理员")
		return
	}

	tradePreCreateRspJson, _ := json.Marshal(tradePreCreateRsp)
	global.Log.Infof("面对面支付响应参数: \n%s", string(tradePreCreateRspJson))
	response.OkWithData(c, response.AlipayTradePreCreateResponse{
		OrderNo: tradePreCreateRsp.Content.OutTradeNo,
		QrCode:  tradePreCreateRsp.Content.QRCode,
	})

}

/*
Face2faceNotify 返回值
// 排查工具
https://opensupport.alipay.com/support/tools/NewCloudparse?ant_source=antsupport

	{
	    "auth_app_id": "2021003191669073",
	    "notify_time": "2023-05-06 14:29:21",
	    "notify_type": "trade_status_sync",
	    "notify_id": "2023050601222142650092371495761067",
	    "app_id": "2021003191669073",
	    "charset": "utf-8",
	    "version": "1.0",
	    "sign_type": "RSA2",
	    "sign": "bft15xXeHmZMIcK2fyhRWTy02VoRo7yXiScu7DgGXmElrrc5YXUzalp1GyQy9jiP/pPnGxQzxmon0sTql89ldwUnvSwJOP6UVChyKKmv5N0buSGUwF8RqHMmtzepeHT0AzT5Wo7xVO54auEbl0Se8tltcMoaHwwVJKF52H5DjlDSLnHHmCQEkWAjISr0P89VyGlFsDZv18lS0wFZWAdU9p5DhX1ukIqXUrS7rEk7FiIRxT76x1EgVfXZ8UCXqW+UBrJF/xJVPy/Q3XIhagnh31crnnNRrmqlivWar1LfXPiPbPx7foCyY6dcLabsrtBDtiRjGjOdeIsXb2CayJnPbg==",
	    "trade_no": "2023050622001492371441858026",
	    "out_trade_no": "1654734155432333312",
	    "out_biz_no": "",
	    "buyer_id": "2088522774692370",
	    "buyer_logon_id": "151****8330",
	    "seller_id": "2088722916977800",
	    "seller_email": "18061877017",
	    "trade_status": "TRADE_SUCCESS",
	    "total_amount": "0.01",
	    "receipt_amount": "0.01",
	    "invoice_amount": "0.01",
	    "buyer_pay_amount": "0.01",
	    "point_amount": "0.00",
	    "refund_fee": "",
	    "subject": "测试使用",
	    "body": "",
	    "gmt_create": "2023-05-06 14:5",
	    "gmt_payment": "2023-05-06 14:26:49",
	    "gmt_refund": "",
	    "gmt_close": "",
	    "fund_bill_list": "[{\"amount\":\"0.01\",\"fundChannel\":\"ALIPAYACCOUNT\"}]",
	    "passback_params": "",
	    "voucher_detail_list": "",
	    "agreement_no": "",
	    "external_agreement_no": ""
	}
*/
func (s *AlipayApi) Face2faceNotify(c *gin.Context) {
	notify, err := global.AlipayClient.GetTradeNotification(c.Request)
	if err != nil {
		global.Log.Errorf("面对面支付回调异常: %v", err)
		response.FailWithStringFailure(c)
		return
	}
	bs, err := json.Marshal(notify)
	if err != nil {
		global.Log.Errorf("面对面支付回调序列化异常: %v", err)
		response.FailWithStringFailure(c)
		return
	}
	global.Log.Infof("支付状态为: %s，面对面支付回调返参: \n%s", notify.TradeStatus, string(bs))

	switch notify.TradeStatus {
	case alipay.TradeStatusSuccess: //（交易支付成功）

		// 查询支付订单信息
		order := new(model.ChatOrderDO)
		err = global.Dao.Model(new(model.ChatOrderDO)).
			Where("id = ? and status = 1 and is_deleted = 0", notify.OutTradeNo).
			First(order).Error

		if err != nil {
			global.Log.Errorf("订单查询异常，订单号: %s", notify.OutTradeNo)
			response.OkWithStringSuccess(c)
			return
		}

		// 查询当前用户绑定的邮箱
		userBindingEmail := new(model.ChatUserBindingEmailCombine)
		err = global.Dao.Table("chat_user_binding_email cubel").
			Select("cubel.username,cu.nickname").
			Joins("left join chat_user_binding cub ON cubel.id = cub.user_binding_email_id ").
			Joins("inner join chat_user cu on cub.user_id = cu.id").
			Where("cub.binding_type = ? and cub.user_id = ? and cubel.verified = 1", constants.BindingTypeEmail, order.UserId).
			First(userBindingEmail).Error
		if err != nil {
			global.Log.Errorf("查询当前用户绑定的邮箱异常，订单号: : %s", notify.OutTradeNo)
			response.OkWithStringSuccess(c)
			return
		}

		// 查询历史
		orderHistory := new(model.ChatOrderHistoryDO)

		err = global.Dao.Model(orderHistory).
			Where("order_id = ? and is_deleted = 0", order.Id).
			First(&orderHistory).Error

		if err != nil {
			global.Log.Errorf("历史订单查询异常，订单号: : %s", notify.OutTradeNo)
			response.OkWithStringSuccess(c)
			return
		}

		// 查询订单项，主要是用来扣减真实库存
		orderItems := make([]model.ChatOrderItemDO, 0)
		err = global.Dao.Model(new(model.ChatOrderItemDO)).
			Where("order_id = ? and is_deleted = 0", order.Id).
			Find(&orderItems).Error
		if err != nil {
			global.Log.Errorf("订单项查询异常，，订单号: %s", notify.OutTradeNo)
			response.OkWithStringSuccess(c)
			return
		}

		productIds := make([]string, 0)
		// productId,exchangeNum
		productExchangeNumMap := make(map[string]int)
		for _, item := range orderItems {
			productIds = append(productIds, item.ProductId)
			_, ok := productExchangeNumMap[item.ProductId]
			if !ok {
				productExchangeNumMap[item.ProductId] = item.ExchangeNum
			} else {
				productExchangeNumMap[item.ProductId] = item.ExchangeNum + productExchangeNumMap[item.ProductId]
			}
		}

		// 根据id集合查询商品信息
		products := make([]model.ChatProductDO, 0)
		err = global.Dao.Model(new(model.ChatProductDO)).
			Where("id in ? and status = 1 and stock > 0 and is_deleted = 0", productIds).
			Find(&products).Error
		if err != nil {
			global.Log.Errorf("商品信息存在异常，订单号: %s，", notify.OutTradeNo)
			response.OkWithStringSuccess(c)
			return
		}

		// productId,stock
		productStockMap := make(map[string]int)
		productSaleMap := make(map[string]int)
		// productId,product
		productMap := make(map[string]model.ChatProductDO)
		for _, product := range products {
			// 库存
			productStockMap[product.Id] = product.Stock
			// 销量
			productSaleMap[product.Id] = product.Sale
			productMap[product.Id] = product
		}
		now := time.Now()
		// 需要绑定用户的兑换码进行保存
		needSaveUserExchangeRecords := make([]model.ChatUserExchangeRecordDO, 0)
		// 需要变更兑换码占用状态进行更新
		needUpdatedExchangeIds := make([]string, 0)
		// productId,[]exchanges
		productExchangeMap := make(map[string][]model.ChatExchangeDO)
		for productId, exchangeNum := range productExchangeNumMap {
			// 查询限制数量的兑换码信息
			exchanges, eErr := s.queryUnusedExchangeLimit(exchangeNum, productId)
			if eErr != nil {
				global.Log.Errorf("商品兑换码不存在，商品ID: %s，需要兑换数量: %d，异常信息: %v", productId, exchangeNum, eErr)
				response.OkWithStringSuccess(c)
				return
			}

			// TODO: 出现的概率很小，一般出现的时候就是脏数据的问题
			if len(exchanges) != exchangeNum {
				global.Log.Errorf("商品兑换码库存不足，商品ID: %s，需要兑换数量: %d", productId, exchangeNum)
				// TODO: 增加兑换码
			}
			exchangeCodes := make([]string, 0)
			for _, exchange := range exchanges {
				needSaveUserExchangeRecords = append(needSaveUserExchangeRecords, model.ChatUserExchangeRecordDO{
					Id:         strconv.Itoa(int(global.IdGenerator.Generate())),
					UserId:     order.UserId,
					ExchangeId: exchange.Id,
					Code:       exchange.Code,
					CreateTime: model.JSONTime{Time: now},
				})
				needUpdatedExchangeIds = append(needUpdatedExchangeIds, exchange.Id)
				exchangeCodes = append(exchangeCodes, exchange.Code)
			}
			productExchangeMap[productId] = append(productExchangeMap[productId], exchanges...)
		}

		// 编程式事务处理，订单、订单历史、兑换码、邮件...
		transactionErr := global.Dao.Transaction(func(tx *gorm.DB) error {

			// 更新订单信息
			err = s.updateOrder(order.Id, now)
			if err != nil {
				return err
			}

			// 订单状态 0:待付款 1:待确认 2:已完成 3:无效订单 4:已关闭 #chat_order
			// 支付状态 0:支付中(0:待付款 1:待确认) 1:支付成功(2:已完成)  2:支付失败(3:无效订单) 3.支付关闭
			err = s.updateOrderHistory(notify.BuyerLogonId, orderHistory.Id, now)
			if err != nil {
				return err
			}

			// 批量保存兑换码
			err = s.batchSaveUserExchanges(needSaveUserExchangeRecords)
			if err != nil {
				return err
			}

			// 批量更新兑换码占用状态
			err = s.batchUpdateExchange(needUpdatedExchangeIds, now)
			if err != nil {
				return err
			}

			// 批量更新商品库存
			err = s.batchUpdateProduct(productExchangeNumMap, productStockMap, productSaleMap, now)
			if err != nil {
				return err
			}

			return nil
		})
		// TODO: 异常订单需要人工处理
		if transactionErr != nil {
			global.Log.Errorf("数据处理异常: %v", transactionErr)
			response.OkWithStringSuccess(c)
			return
		}

		// 发送邮件给用户
		var exchangeSb strings.Builder
		exchangeSb.WriteString("兑换码: ")

		//TODO：
		userExchanges := make([]model.ChatUserProductExchange, 0)
		for productId, exchanges := range productExchangeMap {
			product := productMap[productId]
			codes := make([]string, 0)
			for _, exchange := range exchanges {
				codes = append(codes, exchange.Code)
				exchangeSb.WriteString(fmt.Sprintf("%s ", exchange.Code))
			}
			userExchanges = append(userExchanges, model.ChatUserProductExchange{
				ProductName: product.Name,
				ProductId:   productId,
				UseTimes:    product.RewardUseTimes,
				Price:       product.Price,
				Codes:       codes,
			})
		}

		var body bytes.Buffer
		tmpl, err := template.ParseFiles("templates/exchange_code.html")
		if err != nil {
			global.Log.Errorf("解析模板异常: %v", err)
			response.OkWithStringSuccess(c)
			return
		}
		err = tmpl.Execute(&body, map[string]any{
			"UserExchanges": userExchanges,
			"TotalAmount":   order.TotalAmount,
			"OrderNo":       order.Id,
			"PayTime":       now.Format(constants.TimeFormatFull),
			"ExpireTime":    now.AddDate(0, 0, 90).Format(constants.TimeFormatFull),
			"NickName":      userBindingEmail.Nickname,
		})
		if err != nil {
			global.Log.Errorf("执行模板异常: %v", err)
			response.OkWithStringSuccess(c)
			return
		}
		emailContent := body.String()
		emailConfig := global.Setting.EmailConfig
		emailMessage := gomail.NewMessage()
		emailMessage.SetHeader("From", emailConfig.From)
		emailMessage.SetHeader("To", userBindingEmail.Username)
		emailMessage.SetHeader("Subject", constants.ChatOrderPurchaseSubject)
		emailMessage.SetBody("text/html", emailContent)

		emailSendLog := new(model.SysEmailSendLogDO)
		emailSendLog.MessageId = uuid.New().String()
		emailSendLog.Id = strconv.Itoa(int(global.IdGenerator.Generate()))
		emailSendLog.FromEmailAddress = emailConfig.From
		emailSendLog.ToEmailAddress = userBindingEmail.Username
		emailSendLog.BizType = constants.ChatExchangeCodePurchase
		emailSendLog.RequestIp = util.GetRequestIp(c)
		emailSendLog.Content = body.String()
		emailSendLog.CreateTime = model.JSONTime{Time: now}
		emailSendLog.UpdateTime = model.JSONTime{Time: now}

		err = global.EmailDialer.DialAndSend(emailMessage)

		if err != nil {
			global.Log.Errorf("发送兑换码购买记录邮箱失败，异常信息: %v", err)
			emailSendLog.Status = constants.StatusSendFailed
			emailSendLog.Message = err.Error()
		} else {
			emailSendLog.Status = constants.StatusSendSuccess
			emailSendLog.Message = constants.DefaultSendSuccessMessage
		}
		err = global.Dao.Model(new(model.SysEmailSendLogDO)).Create(emailSendLog).Error
		if err != nil {
			global.Log.Errorf("记录邮箱发送日志，异常信息: %v", err)
		} else {
			global.Log.Infof("订单号: %s，用户ID： %s，购买的兑换码: \n%s，发送邮件完成", order.Id, order.UserId, exchangeSb.String())
		}
		//case alipay.TradeStatusClosed: //（未付款交易超时关闭，或支付完成后全额退款）
		//case alipay.TradeStatusFinished: //（交易结束，不可退款）
		//case alipay.TradeStatusWaitBuyerPay: //（交易结束，不可退款）
		global.Log.Infof("支付订单号: %s，相关信息处理完成", order.Id)
		response.OkWithStringSuccess(c)
	}
}

func (s *AlipayApi) queryUnusedExchangeLimit(exchangeNum int, productId string) ([]model.ChatExchangeDO, error) {
	exchanges := make([]model.ChatExchangeDO, 0)
	offset, limit := util.GetPaginateParams(1, exchangeNum)
	return exchanges, global.Dao.Model(new(model.ChatExchangeDO)).
		Where("product_id= ? and is_occupied = 0 and is_used = 0 and is_deleted = 0",
			productId).
		Offset(offset).
		Limit(limit).
		Find(&exchanges).Error
}

func (s *AlipayApi) batchUpdateProduct(productExchangeNumMap map[string]int, productStockMap map[string]int, productSaleMap map[string]int, now time.Time) error {
	// 更新库存
	needUpdatedProductUpdatesMap := make(map[string]map[string]any)

	// 兑换码数量扣减
	// productId,exchangeNum
	for productId, exchangeNum := range productExchangeNumMap {
		productStockMap[productId] = productStockMap[productId] - exchangeNum
	}
	// 商品销量+1
	for productId, _ := range productSaleMap {
		productSaleMap[productId] = productSaleMap[productId] + 1
	}

	for productId, stock := range productStockMap {
		// 商品库存扣减
		needUpdatedProductUpdatesMap[productId] = map[string]any{
			"Stock":      stock,
			"Sale":       productSaleMap[productId],
			"UpdateTime": model.JSONTime{Time: now},
		}
	}
	// 乐观锁处理，这里就不用悲观锁去处理了
	for productId, productUpdatesMap := range needUpdatedProductUpdatesMap {
		if err := global.Dao.Model(new(model.ChatProductDO)).
			Where("id = ? and stock > 0", productId).
			Updates(productUpdatesMap).Error; err != nil {
			return errors.New(fmt.Sprintf("更新商品库存异常，商品ID: %s: %v", productId, err))
		}
	}
	return nil
}

func (s *AlipayApi) batchUpdateExchange(needUpdatedExchangeIds []string, now time.Time) error {
	exchangeUpdatesMap := map[string]any{
		"IsOccupied":     1,
		"UpdateTime":     model.JSONTime{Time: now},
		"ExpiresDays":    90,
		"ActivationTime": model.JSONTime{Time: now},
	}
	// 批量更新兑换码状态
	if err := global.Dao.Model(new(model.ChatExchangeDO)).
		Where("id in ? and is_deleted = 0 ", needUpdatedExchangeIds).
		Updates(exchangeUpdatesMap).
		Error; err != nil {
		return errors.New(fmt.Sprintf("批量更新兑换码占用状态异常，兑换码ID: %v: %v", needUpdatedExchangeIds, err))
	}
	return nil
}

func (s *AlipayApi) batchSaveUserExchanges(needSaveUserExchangeRecords []model.ChatUserExchangeRecordDO) error {
	if err := global.Dao.Model(new(model.ChatUserExchangeRecordDO)).
		CreateInBatches(&needSaveUserExchangeRecords, len(needSaveUserExchangeRecords)).Error; err != nil {
		return errors.New(fmt.Sprintf("批量保存用户兑换码异常: %v", err))
	}
	return nil
}

func (s *AlipayApi) updateOrderHistory(payAccount string, orderHistoryId string, now time.Time) error {
	orderHistoryUpdatesMap := map[string]any{
		"OrderStatus": 2,
		"PayStatus":   1,
		"PayAccount":  payAccount,
		"UpdateTime":  model.JSONTime{Time: now},
	}
	if err := global.Dao.Model(new(model.ChatOrderHistoryDO)).
		Where("id = ?", orderHistoryId).
		Updates(orderHistoryUpdatesMap).Error; err != nil {
		return errors.New(fmt.Sprintf("更新历史订单信息异常: %v", err))
	}
	return nil
}

func (s *AlipayApi) updateOrder(orderId string, now time.Time) error {
	orderUpdatesMap := map[string]any{
		"Status":     2,
		"PayTime":    now,
		"UpdateTime": model.JSONTime{Time: now},
	}
	// 修改下单的订单信息为已支付
	if err := global.Dao.Model(new(model.ChatOrderDO)).
		Where("id = ?", orderId).
		Updates(orderUpdatesMap).Error; err != nil {
		return errors.New(fmt.Sprintf("更新下单的订单信息为已支付异常: %v", err))
	}
	return nil
}

func (s *AlipayApi) Test(c *gin.Context) {
	data := `{
			  "subject": "测试使用",
			  "total_amount": "0.01",
			  "out_trade_no": "33",
			  "timeout_express": "30m",
			  "signature": "84cf7a4b6fd209066fe9b0de764b42ba182be0c5cdcd5072bb1864cdee4d3d6e"
			}`
	// 先AES成字节数组,再base64成字符串
	encrypt := util.RsaEncrypt([]byte(data), "rsa/public_key.pem")
	fmt.Printf("\nEncoded Message: \n\n%s", encrypt)
	encodeStr := base64.StdEncoding.EncodeToString(encrypt)
	// base64->字节数组->原文
	createRequest := request.EncodeAlipayTradePreCreateRequest{Base64: encodeStr}
	response.OkWithData(c, createRequest)
}
